﻿using Nemerle;
using Nemerle.Assertions;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
using Nemerle.Compiler.Typedtree;
using Nemerle.Text;
using Nemerle.Utility;
 
using System;
using SCG = System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
 
namespace VladimirMatveev.NotifyPropertyChanged
{
    [MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Class, Inherited = false, AllowMultiple = false)]  
    public macro ImplementsNotifyPropertyChanged(tb : TypeBuilder)
    {
        NPCHelper.ImplementInterface(tb);
    }
 
    [MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Class, Inherited = false, AllowMultiple = false)]  
    public macro ImplementsNotifyPropertyChanged(tb : TypeBuilder)
    {
        NPCHelper.FixProperties(tb)
    }
 
    [MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Property, Inherited = false, AllowMultiple = false)]  
    public macro IgnoreProperty(tb : TypeBuilder, property : ParsedProperty)
    {
        NPCHelper.RegisterIgnored(tb, property)
    }
   
    module NPCHelper
    {
        private ignoredProperties : SCG.HashSet[TypeBuilder * string] = SCG.HashSet();
       
        public RegisterIgnored(tb : TypeBuilder, property : ClassMember.Property) : void
        {
            ignore(ignoredProperties.Add(tb, property.Name));
        }      
       
        public FixProperties(tb : TypeBuilder) : void
        {
            def properties = tb
                .GetProperties()
                .Filter(p => !ignoredProperties.Contains(tb, p.Name));
           
            foreach(p is PropertyBuilder in properties)
            {
                def setter = (p.GetSetter() :> MethodBuilder);    
                when (setter != null)
                {
                    setter.Body = <[
                        $(setter.Body);
                        RaisePropertyChanged($(p.Name : string));
                    ]>;
                }
            }
        }
       
        public ImplementInterface(tb : TypeBuilder) : void
        {
            def handlerFieldName = Macros.NewSymbol("PropertyChanged");
            def fieldDecl = <[ decl:
                private mutable $(handlerFieldName.Id : usesite) : PropertyChangedEventHandler;
            ]>;          
 
            def modifyEvent(modifier)
            {
                <[
                mutable tmp;
                mutable h = $(handlerFieldName.Id : usesite);
                do
                {
                    tmp = h;
                    def newHandler = $(modifier)(tmp, value) :> PropertyChangedEventHandler;
                    h = System.Threading.Interlocked.CompareExchange(ref $(handlerFieldName.Id : usesite), newHandler, tmp);
                } while(h != tmp);
                ]>
            }
            def eventDecl = <[decl:
                public event PropertyChanged : PropertyChangedEventHandler 
                {
                    add { $(modifyEvent(<[ Delegate.Combine ]>)); }
                    remove { $(modifyEvent(<[ Delegate.Remove ]>)); }
                }
                ]>;
 
            def raisePropertyChangedMethodDecl = <[ decl:
                protected RaisePropertyChanged(propertyName: string) : void
                {
                    def h = $(handlerFieldName.Id : usesite);
                    when (h != null)
                        h(this, PropertyChangedEventArgs(propertyName));
                }
                ]>;
           
            tb.Define(fieldDecl);
            tb.Define(eventDecl);
            tb.Define(raisePropertyChangedMethodDecl);
            tb.AddImplementedInterface(<[INotifyPropertyChanged]>);
        }
    }
}